﻿using System;
using System.Windows;
using GameEngine.Model.Data;
using PureMVC.Patterns;
using System.Collections.Generic;
using System.Windows.Media;
using Microsoft.DirectX;
using GameEngine.Libs;

namespace GameEngine.Model
{
    public class ThingDataProxy<MyIdType> : DataProxy, IThingDataProxy<MyIdType>
         where MyIdType : IComparable<MyIdType>
    {
        public ThingDataProxy(
            IThingVO<MyIdType> item
            )
            : base(IThingDataProxyConstants<MyIdType>.CreateName(item), item)
        {
        }

        public IThingVO<MyIdType> VO
        {
            get { return this.Data as ThingVO<MyIdType>; }
        }

        private IThingTypeDataProxy<MyIdType> typeDataProxy;
        public IThingTypeDataProxy<MyIdType> TypeDataProxy
        {
            get
            {
                if (typeDataProxy == null)
                    typeDataProxy = ModelFacade<MyIdType>.Helper.GetThingTypeDataProxy(
                        EngineDataProxy<MyIdType>.Instance.Game.ThingTypes[VO.TypeId]);
                return typeDataProxy;
            }
        }

        public IThingVOId<MyIdType> Id
        {
            get { return VO.Id; }
        }

        public IThingTypeVOId TypeId
        {
            get { return VO.TypeId; }
        }

        public IWorldVOId WorldId
        {
            get;
            set;
        }

        public Point MapPosition
        {
            get;
            set;
        }

        public IViewStateVO State
        {
            get
            {
                return VO.State;
            }
            protected set
            {
                if (VO.State != value)
                {
                    VO.State = value;
                    SendNotification(IThingDataProxyConstants<MyIdType>.THINGCHANGED_NOTIFICATION, this, IThingDataProxyConstants<MyIdType>.THINGCHANGEDSTATE_NOTIFICATIONTYPE);
                }
            }
        }

        //Point(1,1) = ne
        public virtual Point Direction
        {
            get { return VO.Direction; }
            set
            {
                VO.Direction = value;
                //SendNotification(IThingDataProxyConstants.THINGCHANGED_NOTIFICATION,
                //    this,
                //    IThingDataProxyConstants.THINGCHANGEDDIRECTION_NOTIFICATIONTYPE);
            }
        }

        public bool MovingWithMouse
        {
            get { return VO.MovingWithMouse; }
            set { VO.MovingWithMouse = value; }
        }

        public virtual Point Position
        {
            get { return VO.Position.Position; }
            set
            {
                VO.Position.Position = value;

                SendNotification(
                    IThingDataProxyConstants<MyIdType>.THINGCHANGED_NOTIFICATION,
                    this,
                    IThingDataProxyConstants<MyIdType>.THINGCHANGEDPOSITION_NOTIFICATIONTYPE);
            }
        }

        public float Height
        {
            get { return VO.Position.Height; }
            set
            {
                VO.Position.Height = value;
                SendNotification(
                    IThingDataProxyConstants<MyIdType>.THINGCHANGED_NOTIFICATION,
                    this,
                    IThingDataProxyConstants<MyIdType>.THINGCHANGEDHEIGHT_NOTIFICATIONTYPE);
            }
        }

        public virtual IThingDataProxy<MyIdType> Replicate()
        {
            IThingVO<MyIdType> newItem = ModelFacade<MyIdType>.Instance.CreateThingVO(
                new ThingVOId<MyIdType>(ModelFacade<MyIdType>.Instance.CreateNewId()) { Name = VO.Id.Name }, VO.TypeId);
            newItem.Position.Height = VO.Position.Height;
            newItem.Position.Position = VO.Position.Position;
            return new ThingDataProxy<MyIdType>(newItem);
        }

        /// <summary>
        /// Change the state according to properties
        /// </summary>
        public virtual void UpdateState() { }

        //para alterar os dados vai enviar um comando com um callback para ver se está ligado ao server

        /// <summary>
        /// Send Data to Remote, child classes should call this method to after changing VOs
        /// </summary>
        /// <param name="parameters"></param>
        /*
        protected void UpdateData(Parameter parameter)
        {
            List<Parameter> list = new List<Parameter>();
            list.Add(parameter);
            UpdateData(list);
        }*/

        /// <summary>
        /// Send Data to Remote, child classes should call this method to after changing VOs
        /// </summary>
        /// <param name="parameters"></param>
        /*
        protected void UpdateData(List<Parameter> parameters) {
            List<ItemVOParameters> list = new List<ItemVOParameters>();
            list.Add(new ItemVOParameters(VO,parameters));
            DataProxy.Instance.SendData(list);
        }*/

        /// <summary>
        /// Data from Remote, child classes should update the VO Data with the new values
        /// </summary>
        /// <param name="data"></param>
        /*
        abstract public void OnUpdateData(List<Parameter> data);
         */

        //public override string Serialize()
        //{
        //    return Serializer.Serialize(this);
        //}
        //TODO tick alterar idade etc
    }

    public class SensingThingDataProxy<ItemVOId> : ThingDataProxy<ItemVOId>, ISensingThingDataProxy<ItemVOId>
         where ItemVOId : IComparable<ItemVOId>
    {
        /// <summary>
        /// To be calculated in every sensibility time interval
        /// </summary>
        public IDictionary<string, List<KeyValuePair<IThingVOId<ItemVOId>, Point>>> ThingsInReach { get; set; }
        /// <summary>
        /// To be setted when direction is changed
        /// </summary>
        public IDictionary<string, List<Point>> InReachMapPositionsTransformedRelative { get; set; }

        public override Point Direction
        {
            get
            {
                return base.Direction;
            }
            set
            {
                base.Direction = value;
                UpdateInReachMapPositions();
            }
        }

        public new ISensingThingTypeDataProxy<ItemVOId> TypeDataProxy
        {
            get
            {
                return base.TypeDataProxy as ISensingThingTypeDataProxy<ItemVOId>;
            }
        }

        public SensingThingDataProxy(
            ISensingThingVO<ItemVOId> item
            )
            : base(item)
        {
            ThingsInReach = new Dictionary<string, List<KeyValuePair<IThingVOId<ItemVOId>, Point>>>();
            InReachMapPositionsTransformedRelative = new Dictionary<string, List<Point>>();
            UpdateInReachMapPositions();
        }

        /// <summary>
        /// Update InReachMapPositionsTransformed according to direction
        /// </summary>
        protected void UpdateInReachMapPositions()
        {
            InReachMapPositionsTransformedRelative.Clear();
            foreach (KeyValuePair<string, List<Point>> sense in ((ISensingThingTypeDataProxy<ItemVOId>)TypeDataProxy).InReachMapPositionsRelative)
            {
                InReachMapPositionsTransformedRelative.Add(sense.Key, new List<Point>());
                foreach (Point p in sense.Value)
                {
                    //http://en.wikipedia.org/wiki/Dot_product

                    Vector2 a = new Vector2((float)Direction.X, (float)Direction.Y);
                    Vector2 b = new Vector2(0, 1);//standart direction (north)

                    //Angle between vectors
                    double angle = Math.Acos(Vector2.DotProduct(a, b) / (a.Length() * b.Length()));

                    //Rotation
                    System.Windows.Media.Matrix transformationGrid = new System.Windows.Media.Matrix();
                    transformationGrid.M11 = Math.Cos(angle);
                    transformationGrid.M12 = -Math.Sin(angle);
                    transformationGrid.M21 = Math.Sin(angle);
                    transformationGrid.M22 = Math.Cos(angle);
                    Point newPosition = transformationGrid.Transform(p);
                    newPosition.X = Math.Round(newPosition.X, 0);
                    newPosition.Y = Math.Round(newPosition.Y, 0);
                    InReachMapPositionsTransformedRelative[sense.Key].Add(new Point((int)newPosition.X, (int)newPosition.Y));
                }
            }
        }
    }
    public class SensingThingDataProxyActions<ItemVOId>
        where ItemVOId : IComparable<ItemVOId>
    {
        public static void Action(EngineDataProxy<ItemVOId> engine, IWorldDataProxy<ItemVOId> world, IThingTypeDataProxy<ItemVOId> itemType, IThingDataProxy<ItemVOId> item)
        {

            SensingThingDataProxy<ItemVOId> sensingThing = (SensingThingDataProxy<ItemVOId>)item;
            SensingThingTypeDataProxy<ItemVOId> sensingThingType = (SensingThingTypeDataProxy<ItemVOId>)itemType;

            foreach (string sense in sensingThingType.Senses)
            {
                //Initialize list if not
                if (!sensingThing.ThingsInReach.ContainsKey(sense))
                    sensingThing.ThingsInReach.Add(sense, new List<KeyValuePair<IThingVOId<ItemVOId>, Point>>());
                else
                    sensingThing.ThingsInReach[sense].Clear();

                foreach (Point mapPointRelative in sensingThing.InReachMapPositionsTransformedRelative[sense])
                {
                    Point mapPoint = new Point(sensingThing.MapPosition.X + mapPointRelative.X, sensingThing.MapPosition.Y + mapPointRelative.Y);
                    if (world.ItemsMapPosition.ContainsKey(mapPoint))
                    {
                        foreach (IThingVOId<ItemVOId> thing in world.ItemsMapPosition[mapPoint])
                            sensingThing.ThingsInReach[sense].Add(
                                new KeyValuePair<IThingVOId<ItemVOId>, Point>(
                                    thing,
                                    mapPoint));
                    }
                }
            }
        }
    }

    public class MovingThingDataProxy<ItemVOId> : SensingThingDataProxy<ItemVOId>, IMovingThingDataProxy<ItemVOId>
         where ItemVOId : IComparable<ItemVOId>
    {

        public bool Moving
        {
            get { return Destination.HasValue; }
        }
        public Point? Destination
        {
            get { return VO.Destination; }
            set
            {
                VO.Destination = value;
                UpdateState();
            }
        }

        public override Point Direction
        {
            get
            {
                return base.Direction;
            }
            set
            {
                base.Direction = value;
                UpdateState();
            }
        }

        public new IMovingThingTypeDataProxy<ItemVOId> TypeDataProxy
        {
            get
            {
                return base.TypeDataProxy as IMovingThingTypeDataProxy<ItemVOId>;
            }
        }

        public MovingThingDataProxy(
            IMovingThingVO<ItemVOId> item
            )
            : base(item)
        {

        }

        public new IMovingThingVO<ItemVOId> VO
        {
            get { return this.Data as IMovingThingVO<ItemVOId>; }
        }

        override public IThingDataProxy<ItemVOId> Replicate()
        {
            IMovingThingVO<ItemVOId> newItem = ModelFacade<ItemVOId>.Instance.CreateThingVO(
                new ThingVOId<ItemVOId>(ModelFacade<ItemVOId>.Instance.CreateNewId()) { Name = VO.Id.Name }, VO.TypeId) as IMovingThingVO<ItemVOId>;
            newItem.Destination = VO.Destination;//TODO parent
            return new MovingThingDataProxy<ItemVOId>(newItem as IMovingThingVO<ItemVOId>);
        }
    }
    public class MovingThingDataProxyActions<ItemVOId>
        where ItemVOId : IComparable<ItemVOId>
    {
        public static void Action(EngineDataProxy<ItemVOId> engine, IWorldDataProxy<ItemVOId> world, IThingTypeDataProxy<ItemVOId> itemType, IThingDataProxy<ItemVOId> item)
        {

            IMovingThingDataProxy<ItemVOId> movingThing = (IMovingThingDataProxy<ItemVOId>)item;
            IMovingThingTypeDataProxy<ItemVOId> movingThingType = (IMovingThingTypeDataProxy<ItemVOId>)itemType;

            if (movingThing.Destination.HasValue)
            {
                Point newDirection = item.Direction;
                Point lastPosition = item.Position;
                Point newDestination;

                newDestination = movingThing.Destination.Value;
                #region comentado
                
                //Path path = world.Map.findPath(null, 
                //    (int)movingThing.Position.X, 
                //    (int)movingThing.Position.Y, 
                //    (int)movingThing.Destination.Value.X, 
                //    (int)movingThing.Destination.Value.Y);               
                //if (path!=null && path.getLength() > 1)
                //{
                //    Path.Step step = path.getStep(1);
                //    newDestination = new Point((double)step.getX(), (double)step.getY());
                //}
                //else {
                //    return;
                //    //newDestination = movingThing.Position;//TODO handle caminhos impossíveis
                //}


                //Point newDestination = new Point(
                //    item.Position.X == movingThing.Destination.Value.X ?
                //        item.Position.X :
                //        item.Position.X > movingThing.Destination.Value.X ?
                //            item.Position.X - movingThingType.Velocity :
                //            item.Position.X + movingThingType.Velocity,
                //    item.Position.Y == movingThing.Destination.Value.Y ?
                //        item.Position.Y :
                //        item.Position.Y > movingThing.Destination.Value.Y ?
                //            item.Position.Y - movingThingType.Velocity :
                //            item.Position.Y + movingThingType.Velocity);

                //Point newMapDestination = new Point(
                //    Math.Round(newDestination.X, 0),
                //    Math.Round(newDestination.Y, 0));
                //Point newMapPosition = new Point(
                //    Math.Floor(newDestination.X),
                //    Math.Floor(newDestination.Y));

                ////check if its occupied
                //if (world.ItemsMapPosition.ContainsKey(newMapDestination))
                //    if (world.ItemsMapPosition[newMapDestination].Value.CompareTo(item.Id.Value) != 0)
                //    {

                //        return;
                //    }

                #endregion

                #region Handle Movement
                //item.Position = newDestination;//pode ser filtrado pelo setter
                var newX = Math.Round(item.Position.X == newDestination.X ?
                        item.Position.X :
                        item.Position.X > newDestination.X ?
                            item.Position.X - movingThingType.Velocity :
                            item.Position.X + movingThingType.Velocity,2);
                var newY = Math.Round(item.Position.Y == newDestination.Y ?
                        item.Position.Y :
                        item.Position.Y > newDestination.Y ?
                            item.Position.Y - movingThingType.Velocity :
                            item.Position.Y + movingThingType.Velocity,2);
                float minDistance = movingThingType.Velocity;
                if (newX > newDestination.X && newX - newDestination.X < minDistance ||
                    newX < newDestination.X && newDestination.X - newX < minDistance)
                    newX = newDestination.X;
                if (newY > newDestination.Y && newY - newDestination.Y < minDistance ||
                    newY < newDestination.Y && newDestination.Y - newY < minDistance)
                    newY = newDestination.Y;

                item.Position = new Point(
                    newX,
                    newY);
                #endregion

                Point newPosition = item.Position;
                
                #region Update Direction 
                if (lastPosition.X < newPosition.X)
                {
                    if (lastPosition.Y == newPosition.Y)
                    {
                        //este
                        newDirection.X = 1;
                        newDirection.Y = 0;
                    }
                    else if (lastPosition.Y > newPosition.Y)
                    {
                        //se
                        newDirection.X = 1;
                        newDirection.Y = -1;
                    }
                    else if (lastPosition.Y < newPosition.Y)
                    {
                        //ne
                        newDirection.X = 1;
                        newDirection.Y = 1;
                    }
                }
                else
                {
                    if (lastPosition.X > newPosition.X)
                    {
                        if (lastPosition.Y == newPosition.Y)
                        {
                            //oeste
                            newDirection.X = -1;
                            newDirection.Y = 0;
                        }
                        else if (lastPosition.Y > newPosition.Y)
                        {
                            //sw
                            newDirection.X = -1;
                            newDirection.Y = -1;
                        }
                        else if (lastPosition.Y < newPosition.Y)
                        {
                            //nw
                            newDirection.X = -1;
                            newDirection.Y = 1;
                        }
                    }
                    else
                    {
                        if (lastPosition.Y > newPosition.Y)
                        {
                            //s
                            newDirection.X = 0;
                            newDirection.Y = -1;
                        }
                        else if (lastPosition.Y < newPosition.Y)
                        {
                            //n
                            newDirection.X = 0;
                            newDirection.Y = 1;
                        }
                    }
                }


                if (item.Direction != newDirection)
                {
                    item.Direction = newDirection;
                }
                #endregion

                // Handle End movement
                if (Math.Abs(Math.Max(movingThing.Destination.Value.X, item.Position.X) - Math.Min(movingThing.Destination.Value.X, item.Position.X)) < 0.099f &&
                    Math.Abs(Math.Max(movingThing.Destination.Value.Y, item.Position.Y) - Math.Min(movingThing.Destination.Value.Y, item.Position.Y)) < 0.099f)
                {
                    movingThing.Position = movingThing.Destination.Value;
                    movingThing.Destination = null;
                }

            }
        }
    }

    public class ThinkingThingDataProxy<ItemVOId> : MovingThingDataProxy<ItemVOId>, IThinkingThingDataProxy<ItemVOId>
         where ItemVOId : IComparable<ItemVOId>
    {
        public new IThinkingThingTypeDataProxy<ItemVOId> TypeDataProxy
        {
            get
            {
                return base.TypeDataProxy as IThinkingThingTypeDataProxy<ItemVOId>;
            }
        }

        public NeuralNetwork Brain
        {
            get { return VO.Brain; }
            set { VO.Brain = value; }
        }

        public ThinkingThingDataProxy(
            IThinkingThingVO<ItemVOId> item
            )
            : base(item)
        {

        }

        public new IThinkingThingVO<ItemVOId> VO
        {
            get { return this.Data as IThinkingThingVO<ItemVOId>; }
        }

        override public IThingDataProxy<ItemVOId> Replicate()
        {
            throw new NotImplementedException();
            //IMovingThingVO<ItemVOId> newItem = ModelFacade<ItemVOId>.Instance.CreateThingVO(
            //    new ThingVOId<ItemVOId>(ModelFacade<ItemVOId>.Instance.CreateNewId()), VO.TypeId) as IMovingThingVO<ItemVOId>;
            //newItem.Destination = VO.Destination;//TODO parent
            //return new MovingThingDataProxy<ItemVOId>(newItem as IMovingThingVO<ItemVOId>);
        }


    }
    public class ThinkingThingDataProxyActions<ItemVOId>
        where ItemVOId : IComparable<ItemVOId>
    {
        public static void Action(EngineDataProxy<ItemVOId> engine, IWorldDataProxy<ItemVOId> world, IThingTypeDataProxy<ItemVOId> itemType, IThingDataProxy<ItemVOId> item)
        {
            IThinkingThingDataProxy<ItemVOId> itemThinking = item as IThinkingThingDataProxy<ItemVOId>;
            IThinkingThingTypeDataProxy<ItemVOId> itemThinkingType = itemType as IThinkingThingTypeDataProxy<ItemVOId>;

            float[] outputs = itemThinking.Brain.Output(itemThinkingType.BrainInput(world, item));
            itemThinkingType.BrainOutput(item, outputs);
        }
    }

    //TODO thinkingthing prolog http://cs-prolog.sourceforge.net/
    //TODO thinkingthing hardcoded
    //TODO controlable
}
